﻿using Jiubang.IO;

namespace Jiubang.Util {
    using System;
    using System.IO;
    using System.Threading;
    using System.Xml.Serialization;
    using log4net;

    /// <summary>
    /// 利用XmlSerializer实现的配置管理器
    /// </summary>
    public static class XmlConfigurator<T> where T : class {
        private static readonly ILog Log;
        private static readonly XmlSerializer Serializer;
        private static int _flag;
        private static LazyFileSystemWatcher _watcher;

        static XmlConfigurator() {
            Log = LogManager.GetLogger(typeof(XmlConfigurator<T>));
            Serializer = new XmlSerializer(typeof(T));
        }

        /// <summary>
        /// 当前配置反序列化后的实例。<br/>
        /// 如ConfigureAndWatch方法的异常未被获取，该值不会为null。
        /// </summary>
        public static T Instance { get; private set; }

        /// <summary>
        /// 从指定的路径加载配置文件并监视其变更
        /// </summary>
        /// <param name="fileFullName">配置文件路径</param>
        /// <param name="configureInfoOnError">加载失败时默认的配置信息。该参数为null时初始加载配置文件失败将抛出异常。</param>
        public static void ConfigureAndWatch(string fileFullName, T configureInfoOnError) {
            if (Interlocked.Increment(ref _flag) > 0)
                throw new Exception("该方法相同泛型参数的被多次调用");

            string directory = Path.GetDirectoryName(fileFullName);
            string filename = Path.GetFileName(fileFullName);

            _watcher = new LazyFileSystemWatcher(directory, filename, WatcherChangeTypes.Changed | WatcherChangeTypes.Created);
            _watcher.Changed += OnFileChanged;

            Log.Info("加载配置文件");
            try {
                LoadConfigure(fileFullName);
            } catch (Exception e) {
                Log.Error("配置文件加载失败", e);
                if (configureInfoOnError == null)
                    throw;
                Instance = configureInfoOnError;
            }

            _watcher.EnableRaisingEvents = true;
        }

        private static void LoadConfigure(string fileFullName) {
            using (var stream = new FileStream(fileFullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                Instance = (T)Serializer.Deserialize(stream);
        }

        private static void OnFileChanged(LazyFileSystemWatcher sender, LazyFileSystemWatcher.EventArgument e) {
            Log.Info("配置文件" + e.History.Last);

            try {
                LoadConfigure(e.FullPath);
            } catch (Exception ex) {
                Log.Error(ex);
            }
        }
    }
}